package app.netmorphgui;

import netmorph.model.parts.ElongOutputModel;
import netmorph.model.parts.ElongInputModel;
import netmorph.model.parts.BESInputModel;
import netmorph.model.parts.BESOutputModel;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.table.TableModel;

import netmorph.model.NetmorphModel;

import netmorph.model.NetmorphExec;
import neuron.NetworkStatistics;
import netmorph.optimization.ElongPartOptimizer;
import model.optimization.ModelOptimization;
import netmorph.optimization.RABESPartOptimizer;
import netmorph.model.parts.TuftElongPart;
import netmorph.model.parts.BasalBEPart;
import netmorph.model.parts.BasalElongPart;
import netmorph.model.parts.ObliqueElongPart;

import common.notification.EmailNotifier;

public class OptimizationDialog extends JFrame {

 
	
	BESInputModel besInputModel;
	BESOutputModel besOutputModel;
	ElongInputModel elongInputModel;
	ElongOutputModel elongOutputModel;
	
	final NetmorphModel model;  


	private JPanel createPanel()
	{
		JPanel pnl = new JPanel();
		pnl.setLayout(new BoxLayout(pnl, BoxLayout.Y_AXIS));
		return pnl;
	}
	
	protected ModelOptimization makeBESOptimizer() throws InstantiationException, IllegalAccessException
	{
		model.setParam("V.pyramidal", "40");
		 
		ModelOptimization opt = new ModelOptimization(model, RABESPartOptimizer.class);
		opt.addModelPart(new BasalBEPart(besInputModel, besOutputModel, model));
//		opt.addModelPart(new BasalSPart(besInputModel, besOutputModel, model));
//		opt.addModelPart(new TuftBEPart(besInputModel, besOutputModel, model));
//		opt.addModelPart(new TuftSPart(besInputModel, besOutputModel, model));
//		opt.addModelPart(new ObliqueBEPart(besInputModel, besOutputModel, model));
//		opt.addModelPart(new ObliqueSPart(besInputModel, besOutputModel, model));

		return opt;
	}
	
	protected ModelOptimization makeElongOptimizer() throws InstantiationException, IllegalAccessException
	{
		model.setParam("V.pyramidal", "40");
		 
		ModelOptimization opt = new ModelOptimization(model, ElongPartOptimizer.class);
		opt.addModelPart(new BasalElongPart(elongInputModel, elongOutputModel, model));
		opt.addModelPart(new TuftElongPart(elongInputModel, elongOutputModel, model));
		opt.addModelPart(new ObliqueElongPart(elongInputModel, elongOutputModel, model));

		return opt;
	}
	
	private JScrollPane makeExcelScrollTable(TableModel model)
	{
		JTable inpt = new JTable(model); // JTable
		new ExcelAdapter(inpt);  // Excel copy-paste support
		JScrollPane jsp = new JScrollPane(inpt);  // Scrollpane
		return jsp;
	}
	
	private JPanel createBasalTopoPanel(NetmorphModel model)
	{
		JPanel pnl = createPanel();
		pnl.setLayout(new BorderLayout());
		
		besInputModel = new BESInputModel(model);
		pnl.add(makeExcelScrollTable(besInputModel), BorderLayout.WEST);
		
		besOutputModel = new BESOutputModel(model);
		pnl.add(makeExcelScrollTable(besOutputModel), BorderLayout.EAST);

		JPanel buttons = new JPanel();
		JButton btn = new JButton("Compute");
		buttons.add(btn);
		final NetmorphModel fmodel = model;  
		btn.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent ev) {
				try {
					fmodel.initRun(NetmorphExec.DEFAULT_DIRNAME + "-x", 0);
					fmodel.run();
				} catch (Exception e) {
					e.printStackTrace();
				}
				besOutputModel.dataChanged();
			}
		});
		
  

		
		final JButton btn4 = new JButton("opt");
		buttons.add(btn4);
		btn4.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent ev) {
				Thread thr = new Thread() {
					public void run() {

						btn4.setEnabled(false);

						try {
							ModelOptimization opt = makeBESOptimizer();
							opt.searchParameters();
						} catch (Exception e) {
							e.printStackTrace();
							try {
								EmailNotifier.notify("Exception: " + e.getMessage());
							} catch (Exception e1) { e1.printStackTrace(); }
						}
						
						btn4.setEnabled(true);
					};
				};
				thr.start();
			}
		});

		
		pnl.add(buttons, BorderLayout.SOUTH);
		
		return pnl;
	}
	
	private void runModel()
	{
		try {
			model.run();
			elongOutputModel.dataChanged();
		} 
		catch (Exception e) {
			JOptionPane.showMessageDialog(this, "Error running netmorph: " + e.getMessage());
		}
	}
	
 
	
	private JPanel createBasalElongPanel()
	{
		JPanel pnl = createPanel();
		pnl.setLayout(new BorderLayout());

		elongInputModel = new ElongInputModel(model);
		pnl.add(makeExcelScrollTable(elongInputModel), BorderLayout.WEST);

		elongOutputModel = new ElongOutputModel(model);
		pnl.add(makeExcelScrollTable(elongOutputModel), BorderLayout.EAST);

		JPanel buttons = new JPanel();
		JButton btn = new JButton("Compute");
		buttons.add(btn);
		btn.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent ev) {
				runModel();
			}
		});
		
		final JButton btn2 = new JButton("Optimize length");
		buttons.add(btn2);
		btn2.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent ev) {
				Thread thr = new Thread() {
					public void run() {
						 
						btn2.setEnabled(false);
			 			
						try {
							ModelOptimization opt = makeElongOptimizer();
							opt.searchParameters();
						} catch (Exception e) {
							e.printStackTrace();
							try {
								EmailNotifier.notify(e.getMessage());
							} catch (Exception e1) { e1.printStackTrace(); }
						}
				
						btn2.setEnabled(true);
					};
				};
				thr.start();
			}
		});
		pnl.add(buttons, BorderLayout.SOUTH);

		return pnl;
	}
	 
	public String makeBESSummary()
	{
		StringBuffer sb = new StringBuffer();

		
		// PARAMETERS
		
		sb.append(String.format("Basal BES : %.3f, %.3f, %.3f    ", 
				besInputModel.getBasalB(),
				besInputModel.getBasalE(),
				besInputModel.getBasalS()));
		sb.append("\n");
		
		sb.append(String.format("Tuft BES : %.3f, %.3f, %.3f    ", 
				besInputModel.getTuftB(),
				besInputModel.getTuftE(),
				besInputModel.getTuftS()));
		sb.append("\n");

		sb.append(String.format("Oblique BES : %.3f, %.3f, %.3f    ", 
				besInputModel.getObliqueB(),
				besInputModel.getObliqueE(),
				besInputModel.getObliqueS()));
		sb.append("\n");

		
		// OUTPUT
		
		sb.append("\n");
		sb.append("Number of Terminal Segments  -  Basal | tuft | Oblique\n");
		
		sb.append(String.format("Target #TS mean(stdev) :  %.3f (%.3f)   %.3f (%.3f)   %.3f (%.3f)  \n",
				besOutputModel.getTargetBTSMean(), besOutputModel.getTargetBTSDev(),
				besOutputModel.getTargetTTSMean(), besOutputModel.getTargetTTSDev(),
				besOutputModel.getTargetOTSMean(), besOutputModel.getTargetOTSDev()
				));
		
		NetworkStatistics stats = model.getLastStats();
		
		sb.append(String.format("Model  #TS mean(stdev) :  %.3f (%.3f)   %.3f (%.3f)   %.3f (%.3f)  \n",
				stats.basalTermSegCountDist().mean(), stats.basalTermSegCountDist().stdev(),
				stats.tuftTermSegCountDist().mean(), stats.tuftTermSegCountDist().stdev(),
				stats.obliqueTermSegCountDist().mean(), stats.obliqueTermSegCountDist().stdev()
				));

		
		sb.append("\n");
		sb.append("Asymmetry index  -  Basal | tuft | Oblique\n");
		
		sb.append(String.format("Target Asym mean(stdev) :  %.3f (%.3f)   %.3f (%.3f)   %.3f (%.3f)  \n",
				besOutputModel.getTargetBAsymMean(), besOutputModel.getTargetBAsymDev(),
				besOutputModel.getTargetTAsymMean(), besOutputModel.getTargetTAsymDev(),
				besOutputModel.getTargetOAsymMean(), besOutputModel.getTargetOAsymDev()
				));
		
		sb.append(String.format("Model  Asym mean(stdev) :  %.3f (%.3f)   %.3f (%.3f)   %.3f (%.3f)  \n",
				stats.basalAsymIndexDist().mean(), stats.basalAsymIndexDist().stdev(),
				stats.tuftAsymIndexDist().mean(), stats.tuftAsymIndexDist().stdev(),
				stats.obliqueAsymIndexDist().mean(), stats.obliqueAsymIndexDist().stdev()
				));

		sb.append("\n\n");
		
		return sb.toString();
	}
	
	
	public String makeElongSummary()
	{
		StringBuffer sb = new StringBuffer();

		
		// PARAMETERS
		
		sb.append(String.format("Basal ERI, Ini : %.10f(%.10f),  %.10f(%.10f)    ", 
				elongInputModel.getBERIPDFMean(), elongInputModel.getBERIPDFStdev(),
				elongInputModel.getBTSEMBranchPDFMean(), elongInputModel.getBTSEMBranchPDFStdev()));
		sb.append("\n");
		
		sb.append(String.format("tuft ERI, Ini : %.10f(%.10f),  %.10f(%.10f)    ", 
				elongInputModel.getTERIPDFMean(), elongInputModel.getTERIPDFStdev(),
				elongInputModel.getTTSEMBranchPDFMean(), elongInputModel.getTTSEMBranchPDFStdev()));
		sb.append("\n");

		sb.append(String.format("Oblique ERI, Ini : %.10f(%.10f),  %.10f(%.10f)    ", 
				elongInputModel.getOERIPDFMean(), elongInputModel.getOERIPDFStdev(),
				elongInputModel.getOTSEMBranchPDFMean(), elongInputModel.getOTSEMBranchPDFStdev()));
		sb.append("\n");

		
		// OUTPUT
		
		sb.append("\n");
		sb.append("Terminal segment length  -  Basal | tuft | Oblique\n");
		
		sb.append(String.format("Target TS Len mean(stdev) :  %.3f (%.3f)   %.3f (%.3f)   %.3f (%.3f)  \n",
				elongOutputModel.getTargetBTSLenMean(), elongOutputModel.getTargetBTSLenStdev(),  
				elongOutputModel.getTargetTTSLenMean(), elongOutputModel.getTargetTTSLenStdev(),  
				elongOutputModel.getTargetOTSLenMean(), elongOutputModel.getTargetOTSLenStdev()  
				));
		
		NetworkStatistics stats = model.getLastStats();
		
		sb.append(String.format("Model  TS Len mean(stdev) :  %.3f (%.3f)   %.3f (%.3f)   %.3f (%.3f)  \n",
				stats.basalTermSegLenDist().mean(), stats.basalTermSegLenDist().stdev(),  
				stats.tuftTermSegLenDist().mean(), stats.tuftTermSegLenDist().stdev(),  
				stats.obliqueTermSegLenDist().mean(), stats.obliqueTermSegLenDist().stdev() 
				));

		sb.append("\n");
		sb.append("Intermediate segment length  -  Basal | tuft | Oblique\n");
		
		sb.append(String.format("Target IS Len mean(stdev) :  %.3f (%.3f)   %.3f (%.3f)   %.3f (%.3f)  \n",
				elongOutputModel.getTargetBISLenMean(), elongOutputModel.getTargetBISLenStdev(),  
				elongOutputModel.getTargetTISLenMean(), elongOutputModel.getTargetTISLenStdev(),  
				elongOutputModel.getTargetOISLenMean(), elongOutputModel.getTargetOISLenStdev()  
				));
				
		sb.append(String.format("Model  IS Len mean(stdev) :  %.3f (%.3f)   %.3f (%.3f)   %.3f (%.3f)  \n",
				stats.basalIntSegLenDist().mean(), stats.basalIntSegLenDist().stdev(),  
				stats.tuftIntSegLenDist().mean(), stats.tuftIntSegLenDist().stdev(),  
				stats.obliqueIntSegLenDist().mean(), stats.obliqueIntSegLenDist().stdev() 
				));

		sb.append("\n\n");
		
		return sb.toString();
	}
	
	private JPanel createAllPanel()
	{
		JPanel pnl = new JPanel();
		pnl.setLayout(new BoxLayout(pnl, BoxLayout.Y_AXIS));
		JLabel lbl = new JLabel("Enter your email address for status updates: ");
		pnl.add(lbl);
		final JTextField txt = new JTextField(80);
		txt.setMaximumSize(new Dimension(800,50));
		pnl.add(txt);
		final JLabel status = new JLabel("Status..");
		pnl.add(status);
		JButton btn = new JButton("Optimize all");
		pnl.add(btn);		
		pnl.add(Box.createGlue());
		
		btn.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent ev) {
				EmailNotifier.to = txt.getText();

				// BES
				try {
					ModelOptimization opt = makeBESOptimizer();
					opt.searchParameters();
					EmailNotifier.notify(makeBESSummary());
				} catch (Exception ex) {ex.printStackTrace();}
				

				// Elong
				try {
					ModelOptimization opt = makeElongOptimizer();
					opt.searchParameters();
					EmailNotifier.notify(makeElongSummary());
				} catch (Exception ex) {ex.printStackTrace();}
			}
		});
		
		return pnl;
	}
	
	public OptimizationDialog(NetmorphModel model) {
		
		super("Parameter optimization");

		this.model = model;
		
		JTabbedPane tab = new JTabbedPane();
		tab.setFont(new Font("Arial", Font.PLAIN, 24));
		add(tab);

		
		JPanel pnl = createBasalTopoPanel(model);
		tab.add("Topological", pnl);
		
		pnl = createBasalElongPanel();
		tab.add("Metrical", pnl);

		pnl = createAllPanel();
		tab.add("All", pnl);

		
		setSize(1000, 512);
		setLocation(150, 150);
		setVisible(true);
	}
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		NetmorphModel nm = new NetmorphModel();
		JFrame jf = new OptimizationDialog(nm);
		jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}

}
